 using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

/*
 * Jose's repair notes
 *      The State's enter function isn't called when the tank states are initialized.
 */

namespace MUSA
{
    #region Enumerators

    //Denotes the tanks current state
    enum TankState
    {
        Scout,
        Offense,
        Retreat,
        Defense,
        InGroup
    }

    enum ActiveStatus
    {
        Active,
        Waiting
    }

    //Denotes the tank's state's mode
        //ex) if in Scout State and Collided, the tank would
            //run the collision reaction logic
    enum StateMode
    {
        Normal,         //running state's execute
        Collided,       //running collision reactive logic
        Hit,            //running fire upon/hit reactive logic
        Sensed          //running sensed enemy reactive logic
    }

    //Movement enum is to be used to specify mechanical movement
    public enum Movement
    {
        MoveTo,
        TurnTo,
        Fire,
        Hunt,
        Stopped,
        Reverse
    }

    #endregion

    #region Support Data Structures

    public class Action
    {
        //command
        public Movement instruction;

        //support data
            //either the goToPosition if instruction is MoveTo
            //or the turnToDirection if instruction is TurnTo
        public Vector3 vectorInformation;
        public float floatVariable;

        public Action(Movement _inst)
        {
            instruction = _inst;
            vectorInformation = Vector3.Zero;
            floatVariable = -1;
        }

        public Action(Movement _inst, Vector3 _vec)
        {
            instruction = _inst;
            vectorInformation = _vec;
            floatVariable = -1;
        }

        public Action(Movement _inst, float _floatVar)
        {
            if (_inst != Movement.Stopped && _inst != Movement.Reverse)
            {
                //for the lulz
                throw new Exception();
            }

            instruction = _inst;

            vectorInformation = Vector3.Zero;
            floatVariable = _floatVar;
        }
    }

    #endregion

    class StateMachine
    {
        #region Member Variables

        Tank mOwner;

        State mCurrentState;    //Current State
        State mPreviousState;   //Previous State
        State mGlobalState;     //Global State to be used for Sensory Function

        public StateMachine(Tank Owner)
        {
            mOwner = Owner;

            mCurrentState = new Scout();
            Owner.CurrentState = TankState.Scout;
            mGlobalState = new Global();
        }

        public StateMachine(Tank Owner, TankState _ts)
        {
            mOwner = Owner;
            mGlobalState = new Global();

            switch (_ts)
            {
                case TankState.Scout:
                    mCurrentState = new Scout();
                    Owner.CurrentState = TankState.Scout;
                    break;
                    
                case TankState.Offense:
                    mCurrentState = new Offense();
                    Owner.CurrentState = TankState.Offense;
                    break;

                case TankState.Defense:
                    mCurrentState = new Defense();
                    Owner.CurrentState = TankState.Defense;
                    break;

                //note: You typically don't want to start with this, but its here for
                    //consistancy
                case TankState.Retreat:
                    mCurrentState = new Retreat();
                    Owner.CurrentState = TankState.Retreat;
                    break;
            }
        }

        #endregion

        #region Properties

        public State CurrentState
        {
            set
            {
                mCurrentState = value;
            }

            /*
             * Added by Jose, part of operation hack and slack
             */
            get
            {
                return mCurrentState;
            }
        }
        public State PreviousState
        {
            set
            {
                mPreviousState = value;
            }
        }
        public State GlobalState
        {
            set
            {
                mGlobalState = value;
            }
        }

        #endregion

        #region Public Functions

        public void Update(GameTime gTime)
        {
            mGlobalState.Execute(gTime , mOwner);
            mCurrentState.Execute(gTime , mOwner);
        }

        public void ChangeState(GameTime gTime, State newState)
        {
            //Exit old state
            mCurrentState.Exit(gTime, mOwner);

            //Make it the Previous State
            mPreviousState = mCurrentState;

            //Set the New State
            mCurrentState = newState;

            //Enter the New State
            mCurrentState.Enter(gTime, mOwner);
        }

        #endregion
    }

    abstract class State
    {
        /*
         * AI structure notes - 8/1/07
         * actionQueue is a two element array
         *  [0] list of Actions to be executed as a list
         *          stored when other input is added
         *  [1] list of reactive actions
         *          Actions taken upon sensory input(collision, hit, sight)
         *          overidden by new sensory input
         */
        
        protected StateMode stateMode;            //specifies reactive state
        protected int stateIndex;                 //specifies which queue to access in the actionQueue

        protected ActiveStatus activeStatus;

        protected Queue<Action>[] actionQueue;    //stores lists of commands

        //precision variables, error variance acceptablility
        protected float distancePrecision = 0.1f;
        protected float anglePrecision = MathHelper.ToRadians(1.0f);

        public State()
        {
            stateMode = StateMode.Normal;
            stateIndex = 0;

            activeStatus = ActiveStatus.Waiting;

            actionQueue = new Queue<Action>[2];
            actionQueue[0] = new Queue<Action>();
            actionQueue[1] = new Queue<Action>();
        }

        public abstract void Enter(GameTime gTime, Tank mTank);
        public abstract void Execute(GameTime gTime, Tank mTank); 
        public abstract void Exit(GameTime gTime, Tank mTank);

        #region Sensory Input

        /*
         * Functions should be fill out list/stack/queue structure of command
         * execution, once I figure out.
         */

        //added by Jose for evil purposes...maybe
        //sensor bounding sphere input
        public virtual void sensorInput(Tank mTank, Object sensedObject)
        {
        }

        //added by Jose for evil purposes...definitely
        //collision input
        public virtual void collisionInput(Tank mTank, Object collidedObject)
        {

            Vector3 inbetweenVec;

            inbetweenVec = mTank.Position - collidedObject.Position;
            inbetweenVec.Normalize();

            Vector3 npos = mTank.Position + (inbetweenVec * 3);

            Action act = new Action(Movement.MoveTo, npos);
            stateIndex = 1;
            actionQueue[stateIndex].Clear();
            actionQueue[stateIndex].Enqueue(act);



            //Vector3 v1, v2;     //directions, of tank and between collided
            //float angle;        //angle relative to direction and position

            //v1 = mTank.Direction;

            //v2 = collidedObject.Position - mTank.Position;
            //v2.Normalize();

            ////angle = MathHelper.ToDegrees((float)(Math.Atan2(v2.Y, v2.X) - Math.Atan2(v1.Y, v1.X)));
            //angle = MathHelper.ToDegrees((float)(Math.Atan2(v1.Y, v1.X) - Math.Atan2(v2.Y, v2.X)));

            ////switch to secondart element of command queue
            //stateIndex = 1;

            //actionQueue[stateIndex].Clear();

            //if (angle <= 45 && angle >= -45)
            //{
            //    Action act = new Action(Movement.Reverse, 3);
            //    actionQueue[stateIndex].Enqueue(act);

            //    Vector3 newPos = new Vector3(Rand.random(-5, 5), Rand.random(-5, 5), 0);
            //    newPos += mTank.Position;

            //    act = new Action(Movement.MoveTo, newPos);
            //    actionQueue[stateIndex].Enqueue(act);
            //}
            //else if( ( angle <= -45 && angle >= -135 ) || ( angle >= 45 && angle <= 135 ) )
            //{
            //    //Vector3 newPos = mTank.Position + ( mTank.Direction * Rand.random(-3.0f, 3.0f));

            //    //Action act = new Action(Movement.MoveTo, newPos);
            //    //actionQueue[stateIndex].Enqueue(act);

            //    Vector3 v = mTank.Position - collidedObject.Position;
            //    v.Normalize();

            //    v *= Rand.random(1.0f, 3.0f);
            //    Vector3 newPos = mTank.Position + v;

            //    Action act = new Action(Movement.MoveTo, newPos);
            //    actionQueue[stateIndex].Enqueue(act);
            //}
            //else
            //{
            //    Vector3 newVec = mTank.Position - collidedObject.Position;
            //    newVec.Normalize();

            //    newVec = mTank.Position + (newVec * Rand.random(-5.0f, 5.0f));

            //    Action act = new Action(Movement.MoveTo, newVec);
            //    actionQueue[stateIndex].Enqueue(act);
            //}
        }

        //added by Jose for evil purposes...and cake...
        //collision with wall
        public virtual void collisionInput(Tank mTank, Plane collidedPlane)
        {
            Vector3 pos = mTank.Position + (collidedPlane.Normal * 5);
            stateIndex = 1;
            actionQueue[stateIndex].Enqueue(new Action(Movement.MoveTo, pos));
        }

        //added by Jose for evil purposes...I don't know what to add
        //porjectile impact input
        public virtual void hitInput(Tank mTank, object pojectileObject)
        {
        }
        
        #endregion

        #region Mechanical Movement

        /*
         * ProcessAction v2(v1 was deleted)
         *      v2 is also in no real way related to v1
         *  is taken straight from scout and generalized so that all state variances
         *  can use it, in theory.
         */
        protected void ProcessAction(GameTime gTime, Tank mTank)
        {
            //variable declarations
            Action currentAction;
            bool result;

            //value assingment
            currentAction = actionQueue[stateIndex].Peek();
            result = false;

            //switch for instruction
            switch (currentAction.instruction)
            {
                case Movement.MoveTo:

                    //MoveTo(gTime, mTank, currentAction.vectorInformation, result);
                    result = MoveTo(gTime, mTank, currentAction.vectorInformation);
                    break;

                case Movement.TurnTo:

                    //TurnTo(gTime, mTank, currentAction.vectorInformation, result);
                    result = TurnTo(gTime, mTank, currentAction.vectorInformation);
                    break;
                
                case Movement.Fire:
                    result = Fire(gTime, mTank);
                    break;

                case Movement.Stopped:

                    currentAction.floatVariable -= (float)gTime.ElapsedGameTime.TotalSeconds;

                    if(currentAction.floatVariable <= 0)
                    {
                        result = true;
                    }

                    break;

                case Movement.Reverse:

                    //reverse code needs major work
                        //overahaul

                    result = Reverese(gTime, mTank, ref currentAction.floatVariable);

                    break;

                default:
                    break;
            }

            //check if the desired action was completeted
            if (result)
            {
                actionQueue[stateIndex].Dequeue();
            }

            //check for empty action queue
            //  whether immediate action or longterm action is checked within if statement
            if (actionQueue[stateIndex].Count == 0)
            {

                //check if current active actionQueue is the immediate execution calls queue
                if (stateIndex == 1)
                {
                    stateIndex = 0;             //return to normal actionQueue
                    Execute(gTime, mTank);      //re-execute Executte
                    return;                     //exit call
                }
                else
                {
                    activeStatus = ActiveStatus.Waiting;    //enter waiting state
                    return;                                 //exit call
                }
            }
        }

        /*
         * The following MoveTo and TurnTo are interfaces, they are written for asthetic purposed
         * to reduce confusion
         * the bool result parameter return whether the action was finished or if it is still in
         * progress
         */

        //candidate function to be axed
        //public virtual void MoveTo(GameTime gtime, Tank mTank, Vector3 gotoPoint, bool result)
        //{
        //    result = MoveTo(gtime, mTank, gotoPoint);
        //}

        //candidate function to be axed
        //public virtual void TurnTo(GameTime gtime, Tank mTank, Vector3 direction, bool result)
        //{
        //    result = TurnTo(gtime, mTank, direction);
        //}

        protected virtual bool MoveTo(GameTime gtime, Tank mTank, Vector3 gotoPoint)
        {
            Vector3 vecTogoto;      //vector to the goto point
            float distanceToGoto;   //distance to the goto point

            vecTogoto = gotoPoint - mTank.Position;
            distanceToGoto = vecTogoto.Length();

            //check if tank is within allowable precision range
            //for movemnent actions
            if (distanceToGoto <= distancePrecision)
            {
                mTank.Position = gotoPoint;
                return true;
            }

            vecTogoto.Normalize();

            if (TurnTo(gtime, mTank, vecTogoto))
            {
                if (mTank.Forward(gtime, distanceToGoto))
                {
                    return true;
                }

                return false;
            }

            return false;
        }

        protected virtual bool TurnTo(GameTime gtime, Tank mTank, Vector3 direction)
        {
            float angle = (float)(Math.Atan2(direction.Y, direction.X) - Math.Atan2(mTank.Direction.Y, mTank.Direction.X));

            if (Math.Abs(angle) < anglePrecision)
            {
                mTank.Direction = direction;
                return true;
            }

            if (angle < 0)
            {
                return mTank.turnRight(gtime, Math.Abs(angle));
            }
            else
            {
                return mTank.turnLeft(gtime, angle);
            }
        }

        protected virtual bool Fire(GameTime gtime, Tank mTank)
        {
            return mTank.fire(gtime);
        }

        protected virtual bool Reverese(GameTime gtime, Tank mTank, ref float reverseDistance)
        {
            reverseDistance -= (float)gtime.ElapsedGameTime.TotalSeconds * mTank.MoveSpeed;
            return mTank.Reverse(gtime, reverseDistance);
        }

        #endregion
    }


    /*
     * may not have a use, suppose to be used as an inbetween
     */
    class Global : State
    {
        public override void Enter(GameTime gTime, Tank mTank)
        {
        }

        public override void Execute(GameTime gTime, Tank mTank)
        {
            Messages currentMessage = mTank.ProcessedMessage;
        }

        public override void Exit(GameTime gTime, Tank mTank)
        {
        }
    }

    class Scout : State
    {
        public Scout() : base()
        {

        }

        public override void Enter(GameTime gTime, Tank mTank)
        {
        }

        /*
         * Scout Execution structure
         */
        public override void Execute(GameTime gTime, Tank mTank)
        {
            mTank.changeColor(Color.Gray);

            //check if the tank is currently attempting to do something
            if( activeStatus == ActiveStatus.Waiting )
            {
                //retrieve new instructions

                Vector3 randPoint;

                randPoint = new Vector3(Rand.random(-20, 20), Rand.random(-20, 20), 0);

                Action newAct = new Action(Movement.MoveTo, randPoint);

                actionQueue[0].Enqueue(newAct);
            }

            ProcessAction(gTime, mTank);
        }

        public override void Exit(GameTime gTime, Tank mTank)
        {
        }

        public override void sensorInput(Tank mTank, Object sensedObject)
        {
        }

        /*
         * note: We may later want to move the withDrawDistance into the State
         *  as a variable, since each tank state will more than likely have a 
         *  different reaction to collision and the amount of space it needs 
         *  to clear the collided tank
         */

        //called in event of Tank's collision with another Tank
        public override void collisionInput(Tank mTank, Object collidedObject)
        {
            //Vector3 v1, v2;     //directions, of tank and between collided
            //float angle;        //angle relative to direction and position

            //v1 = mTank.Direction;   
            //v2 = collidedObject.Position - mTank.Position;
            //v2.Normalize();

            ////angle between v1 and v2
            //angle = MathHelper.ToDegrees( (float) (Math.Atan2(v2.Y, v2.X) - Math.Atan2(v1.Y, v1.X)) );

            ////switch to secondart element of command queue
            //stateIndex = 1;

            
            //if (angle <= 45 && angle >= -45)
            //{
            //    //reverse
            //    Action act = new Action(Movement.Reverse, 3);
            //    actionQueue[stateIndex].Enqueue(act);
            //}
            //else
            //{
            //    Vector3 newVec = mTank.Position - collidedObject.Position;
            //    newVec.Normalize();

            //    newVec = mTank.Position + (newVec * 5);

            //    Action act = new Action(Movement.MoveTo, newVec);
            //    actionQueue[stateIndex].Enqueue(act);
            //}
        }
    }

    class Offense : State
    {
        //support variables
        Object target = null;

        public Object TARGET
        {
            set
            {
                target = value;
            }
        }

        public override void Enter(GameTime gTime, Tank mTank)
        {
            target = null;
        }

        public override void Execute(GameTime gTime, Tank mTank)
        {
            //if waiting for comand, and does not have a target
            if (activeStatus == ActiveStatus.Waiting && target == null)
            {
                //retrieve new instructions
                Vector3 randPoint;
                randPoint = new Vector3(Rand.random(-20, 20), Rand.random(-20, 20), 0);
                Action newAct = new Action(Movement.MoveTo, randPoint);
                actionQueue[0].Enqueue(newAct);

                //mTank.setState(gTime, new InGroup());
            }

            else
            {
                //check if target is available and if tank is within the standard command list
                if (target != null && stateIndex == 0)
                {
                    /*
                    actionQueue[stateIndex].Clear();

                    //reissue new coomand everyturn
                    Vector3 enemyPosition;

                    enemyPosition = target.Position;

                    Action newAct = new Action(Movement.MoveTo, enemyPosition);
                    actionQueue[0].Enqueue(newAct);

                    stateIndex = 0;
                    */

                    

                    //clear previous commands, for new input
                    actionQueue[stateIndex].Clear();

                    Vector3 vectorToTarget;
                    float distanceToTarget;

                    vectorToTarget = target.Position - mTank.Position;
                    distanceToTarget = vectorToTarget.Length();

                    if (distanceToTarget < 5)
                    {
                        actionQueue[stateIndex].Enqueue(new Action(Movement.Fire));
                    }
                    else
                    {
                        actionQueue[stateIndex].Enqueue(new Action(Movement.MoveTo, target.Position));
                    }

                }

                try
                {
                    if (((Tank)target).Dead)
                    {
                        target = null;
                    }
                }
                catch
                {
                    target = null;
                }
            }

            ProcessAction(gTime, mTank);
        }

        public override void Exit(GameTime gTime, Tank mTank)
        {
        }

        public override void sensorInput(Tank mTank, Object sensedObject)
        {
            //update base
            base.sensorInput(mTank, sensedObject);

            Tank t = (Tank)sensedObject;

            //check if new Target is available
            if (target == null && t.Team != mTank.Team)
            {
                //set target
                target = sensedObject;

                //switch activeStatus to active
                    //precaution in case tank is inactive
                activeStatus = ActiveStatus.Active;
            }

        }

        //public override void collisionInput(Tank mTank, Object collidedObject)
        //{
        //    //Vector3 reverse;
        //    //Vector3 withdrawPosition;

        //    //reverse = mTank.Position - collidedObject.Position;
        //    //reverse.Normalize();

        //    //withdrawPosition = mTank.Position + (reverse * 1);

        //    //Action act = new Action(Movement.MoveTo, withdrawPosition);

        //    //stateIndex = 1;

        //    //actionQueue[stateIndex].Enqueue(act);

        //    stateIndex = 1;
        //    actionQueue[stateIndex].Clear();
        //    actionQueue[stateIndex].Enqueue(new Action(Movement.Reverse, 10));
        //}
    }

    class Retreat : State
    {
        public override void Enter(GameTime gTime, Tank mTank)
        {
        }

        public override void Execute(GameTime gTime, Tank mTank)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void Exit(GameTime gTime, Tank mTank)
        {
        }
    }

    class Defense : State
    {

        public override void Enter(GameTime gTime, Tank mTank)
        {
        }

        public override void Execute(GameTime gTime, Tank mTank)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void Exit(GameTime gTime, Tank mTank)
        {
        }
    }

    class Standby : State
    {
        public override void Enter(GameTime gTime, Tank mTank)
        {
        }

        public override void Execute(GameTime gTime, Tank mTank)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public override void Exit(GameTime gTime, Tank mTank)
        {
        }
    }

    //hoachange
    class InGroup : State
    {
        GroupLeader mGroupLeader;

        public override void Enter(GameTime gTime, Tank mTank)
        {

        }

        public override void Execute(GameTime gTime, Tank mTank)
        {
            //check if the tank is currently attempting to do something
            if (activeStatus == ActiveStatus.Waiting)
            {
                Vector3 mCurrentPoint = mGroupLeader.CurrentPoint;

                //Generate New Point Around Group Leader
                Vector3 randPoint = new Vector3(Rand.random((mCurrentPoint.X - 10), (mCurrentPoint.X + 10)),
                                        Rand.random((mCurrentPoint.Y - 10), (mCurrentPoint.Y + 10)), 0);
                //Set New Point to a Tank
                Action nAct = new Action(Movement.MoveTo, randPoint);

                actionQueue[0].Enqueue(nAct);
            }

            ProcessAction(gTime, mTank);
        }


        public override void sensorInput(Tank mTank, Object sensedObject)
        {
            Tank t = (Tank)sensedObject;

            if (t.Team != mTank.Team)
            {
                mTank.setState(MUSA.globalGameTime, new Offense());
                //((Offense)mTank.CurrentState).TARGET = sensedObject;
            }
        }


        public override void Exit(GameTime gTime, Tank mTank)
        {
            mGroupLeader = null;
        }

        /// <summary>
        /// Set the Group Leader
        /// </summary>
        /// NOTES:  SET THE GROUP LEADER
        /// <param name="pGroupLeader"></param>
        public void SetGroupLeader(GroupLeader pGroupLeader)
        {
            mGroupLeader = pGroupLeader;
        }
    }
}
